package tk.winshu.shortestpath.view;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tk.winshu.shortestpath.algorithm.Dijkstra;
import tk.winshu.shortestpath.model.Node;
import tk.winshu.shortestpath.util.ArrowDrawUtil;
import tk.winshu.shortestpath.util.MoveUtil;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author Jason Krebs
 * @date 2015年2月2日
 */
public class CanvasPanel extends JPanel {

    public interface IRunningFinishedListener {
        /**
         * 回调接口
         */
        void onRunningFinished();
    }

    static final Image NODE_IMAGE = Toolkit.getDefaultToolkit().getImage(CanvasPanel.class.getResource("/image/CrystalCircle.png"));
    private static final Image RUNNER = Toolkit.getDefaultToolkit().getImage(CanvasPanel.class.getResource("/image/Runner.png"));
    private static final Logger log = LoggerFactory.getLogger(CanvasPanel.class.getName());

    /**
     * 所有节点
     */
    private List<Node> nodes;

    /**
     * 焦点节点
     */
    private Node focusedNode;
    /**
     * 运行者
     */
    private Node runner;

    /**
     * 起点节点
     */
    private Node startNode;

    /**
     * 终点节点
     */
    private Node endNode;

    /**
     * 临时起点（绘制时使用）
     */
    private Point2D tempStartPoint;

    /**
     * 是否有向图
     */
    private boolean isDirectedGraph;
    /**
     * 是否显示权重
     */
    private boolean isWeightVisible;
    /**
     * 是否循环运行
     */
    private boolean keepLoopRunning;
    /**
     * 是否正在运行
     */
    private boolean isRunning;

    /**
     * 回调监听器
     */
    private IRunningFinishedListener runningFinishedListener;

    CanvasPanel() {
        nodes = new ArrayList<>();

        // 监听鼠标释放动作，用于绘制节点和连线
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if (e.getButton() == MouseEvent.BUTTON1) {
                    if (tempStartPoint == null || getFocusedNode() == null) {
                        return;
                    }
                    // 获取临时点所在节点，若不为空，则可以创建一条连线关系
                    Node tempStartNode = getNode(tempStartPoint);
                    if (tempStartNode != null) {
                        tempStartNode.addStartNode(getFocusedNode());
                        getFocusedNode().addEndNode(tempStartNode);
                    }
                }
                // 重置临时点
                if (tempStartPoint != null) {
                    tempStartPoint = null;
                }
            }
        });
        // 监听鼠标轨迹动作，用于绘制直线和更新焦点节点
        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                createLine(e.getPoint());
            }

            // 鼠标移动时，更新焦点节点
            @Override
            public void mouseMoved(MouseEvent e) {
                Node beforeFocusedNode = focusedNode;
                focusedNode = getNode(e.getPoint());
                if (beforeFocusedNode != focusedNode) {
                    repaint();
                }
            }
        });
    }

    /**
     * 清空画板
     */
    void cleanAll() {
        focusedNode = null;
        startNode = null;
        endNode = null;
        runner = null;
        tempStartPoint = null;
        nodes.clear();
        repaint();
    }

    /**
     * 清空所有连线
     */
    void cleanLines() {
        for (Node node : nodes) {
            node.cleanRelations();
        }
        repaint();
    }

    /**
     * 绘制连线
     */
    private void drawLine(Graphics2D g2) {
        g2.setColor(Color.GREEN);
        for (Node node : nodes) {
            for (Node endNode : node.getEndNodes()) {
                Point2D a = node.getLocation();
                Point2D b = endNode.getLocation();
                drawLine(a, b, g2);
                // 绘制权重
                if (isWeightVisible) {
                    g2.drawString(String.format("%.2f", node.distanceTo(endNode)), (float) (a.getX() + b.getX()) / 2, (float) (a.getY() + b.getY()) / 2);
                }
            }
        }
        // 绘制临时点到焦点的直线
        if (tempStartPoint != null && getFocusedNode() != null) {
            Node node = getNode(tempStartPoint);
            g2.setColor(node == null ? Color.RED : Color.YELLOW);
            drawLine(getFocusedNode().getLocation(), tempStartPoint, g2);
        }
    }

    private void drawLine(Point2D a, Point2D b, Graphics2D g2) {
        if (isDirectedGraph) {
            ArrowDrawUtil.draw(a, b, g2);
        } else {
            g2.draw(new Line2D.Double(a, b));
        }
    }

    private void drawNode(Graphics2D g2d) {
        for (Node node : nodes) {
            g2d.setColor(Color.WHITE);
            node.draw(g2d);

            if (node.equals(startNode)) {
                g2d.setColor(Color.CYAN);
                g2d.draw(node.getShape());
            }
            if (node.equals(endNode)) {
                g2d.setColor(Color.YELLOW);
                g2d.draw(node.getShape());
            }
            if (node.equals(focusedNode)) {
                g2d.setColor(Color.WHITE);
                g2d.draw(node.getShape());
            }
        }
    }

    private void drawPlayer(Graphics2D g2d) {
        if (runner != null) {
            runner.draw(g2d);
        }
    }

    /**
     * 交换起点和终点
     */
    void exchange() {
        Node tempNode = startNode;
        startNode = endNode;
        endNode = tempNode;
        repaint();
    }

    /**
     * 创建直线
     */
    private void createLine(Point2D point) {
        if (getFocusedNode() == null) {
            return;
        }
        Node tempStartNode = getNode(point);
        if (tempStartNode == null || getFocusedNode().equals(tempStartNode)) {
            tempStartPoint = point;
            repaint();
            return;
        }
        tempStartPoint = tempStartNode.getLocation();
        repaint();
    }

    /**
     * 在指定点创建节点
     */
    void createNode(Point2D point) {
        Node node = new Node(nextNodeIndex(), point, NODE_IMAGE);
        nodes.add(node);
        repaint();
    }

    List<Node> getAllNodes() {
        return Collections.unmodifiableList(nodes);
    }

    Node getFocusedNode() {
        return focusedNode;
    }

    int getLineCount() {
        int count = 0;
        for (Node node : nodes) {
            count += node.getEndNodes().size();
        }
        return count;
    }

    /**
     * 生成下一个节点的标识ID
     */
    private int nextNodeIndex() {
        return nodes.isEmpty() ? 1 : nodes.get(nodes.size() - 1).getIndex() + 1;
    }

    /**
     * 根据位置获取节点，如果没找到则返回null
     */
    Node getNode(Point2D point) {
        if (point == null) {
            return null;
        }
        for (Node node : nodes) {
            if (node.isContainsPoint(point)) {
                return node;
            }
        }
        return null;
    }

    int getNodeCount() {
        return nodes.size();
    }

    boolean isRunnable() {
        if (isRunning) {
            return false;
        }
        if (startNode == null || endNode == null || startNode == endNode) {
            return false;
        }
        return getLineCount() > 0;
    }

    void loadNodes(List<Node> nodes) {
        this.nodes.clear();
        this.nodes.addAll(nodes);
        repaint();
    }

    /**
     * 移动节点
     */
    void moveNode(Point2D point) {
        if (focusedNode == null) {
            return;
        }
        focusedNode.setLocation(point.getX(), point.getY());
        // runner 在 当前焦点节点上，一并移动
        if (runner != null && runner.getIndex() == focusedNode.getIndex()) {
            runner.setLocation(point);
        }
        repaint();
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        drawNode((Graphics2D) g);
        drawLine((Graphics2D) g);
        drawPlayer((Graphics2D) g);
    }

    /**
     * 移除焦点节点的连线
     */
    boolean removeLine() {
        if (focusedNode == null) {
            return false;
        }
        for (Node in : focusedNode.getStartNodes()) {
            in.removeNode(focusedNode);
        }
        for (Node out : focusedNode.getEndNodes()) {
            out.removeNode(focusedNode);
        }
        focusedNode.cleanRelations();
        repaint();

        return true;
    }

    /**
     * 移除焦点节点
     */
    boolean removeNode() {
        if (focusedNode == null) {
            return false;
        }
        for (Node n : focusedNode.getAllRelations()) {
            n.removeNode(focusedNode);
        }
        nodes.remove(focusedNode);
        repaint();
        return true;
    }

    /**
     * 重置运行者
     */
    private void resetRunner() {
        if (nodes.isEmpty()) {
            return;
        }
        if (runner == null) {
            Node node = nodes.get(0);
            runner = new Node(node.getIndex(), node.getLocation(), RUNNER);
        }
        if (startNode != null) {
            runner.setLocation(startNode.getLocation());
            runner.setIndex(startNode.getIndex());
        }
    }

    /**
     * 运行移动动画
     */
    void runAction() {
        isRunning = true;

        Dijkstra dijkstra = new Dijkstra(isDirectedGraph);
        List<Node> path = dijkstra.find(startNode, endNode);
        if (path.isEmpty()) {
            log.warn("找不到路径");
            stop();
            return;
        }
        Thread thread = new Thread(() -> {
            do {
                resetRunner();
                for (Node node : path) {
                    // 中断操作
                    if (!isRunning) {
                        break;
                    }
                    runToNode(node);
                }
            } while (isRunning && keepLoopRunning);
            // 停止运行
            stop();
        });
        thread.setDaemon(true);
        thread.start();
    }

    private void runToNode(Node nextNode) {
        runner.setIndex(nextNode.getIndex());
        Point2D source = runner.getLocation();
        Point2D target = nextNode.getLocation();

        while (!nextNode.getLocation().equals(runner.getLocation())) {
            Point2D nextLocation = MoveUtil.nextLocation(runner.getLocation(), source, target);
            runner.setLocation(nextLocation);
            repaint();
            try {
                Thread.sleep(17);
            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
                Thread.currentThread().interrupt();
            }
        }
    }

    void setDirectedGraph(boolean isDirectedGraph) {
        this.isDirectedGraph = isDirectedGraph;
        repaint();
    }

    void setKeepLoopRunning(boolean isLoopRunning) {
        this.keepLoopRunning = isLoopRunning;
    }


    void setEnd() {
        this.endNode = focusedNode;
        if (this.startNode == focusedNode) {
            this.startNode = null;
        }
    }

    void setOnRunningFinished(IRunningFinishedListener runningFinishedListener) {
        this.runningFinishedListener = runningFinishedListener;
    }

    void setWeightVisible(boolean weightVisible) {
        this.isWeightVisible = weightVisible;
        repaint();
    }

    void markFocusedAsStartNode() {
        this.startNode = focusedNode;
        if (this.endNode == focusedNode) {
            this.endNode = null;
        }
    }

    /**
     * 停止当前正在运行的
     */
    void stop() {
        if (isRunning) {
            isRunning = false;
            if (runningFinishedListener != null) {
                runningFinishedListener.onRunningFinished();
            }
        }
    }

}
